/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "PLJavaFile.h"

#include <stdio.h>
#include <string.h>
#include <filetypes/PLJavaConstClassInfo.h>
#include <filetypes/PLJavaConstRefInfo.h>
#include <filetypes/PLJavaConstTypesString.h>
#include <filetypes/PLJavaConstTypesInteger.h>
#include <filetypes/PLJavaConstTypesFloat.h>
#include <filetypes/PLJavaConstTypesLong.h>
#include <filetypes/PLJavaConstTypesDouble.h>
#include <filetypes/PLJavaConstNameAndType.h>
#include <filetypes/PLJavaFieldInfo.h>
#include <filetypes/PLJavaAttribute.h>
#include <filetypes/PLJavaMethodeInfo.h>
#include <filetypes/PLJavaConstTypesUtf8.h>
#include <filetypes/PLJavaAttributeCode.h>
#include <filetypes/PLJavaAttributeLineNumberTable.h>
#include <filetypes/PLJavaAttributeLineNumberInfo.h>
#include <filetypes/PLJavaAttributeSourceFile.h>
#include <filetypes/PLJavaAttributeStackMapTable.h>
#include <filetypes/PLJavaAttributeStackMapSame.h>
#include <filetypes/PLJavaAttributeStackMapSameLocals1StackItem.h>
#include <filetypes/PLJavaAttributeStackMapSameLocals1StackItemExtended.h>
#include <filetypes/PLJavaAttributeStackMapChop.h>
#include <filetypes/PLJavaAttributeStackMapSameFrameExtended.h>
#include <filetypes/PLJavaAttributeStackMapAppend.h>
#include <filetypes/PLJavaAttributeStackMapFullFrame.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoTop.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoInteger.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoFloat.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoLong.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoDouble.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoNull.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoUninitializedThis.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoObjectVariableInfo.h>
#include <filetypes/PLJavaAttributeStackMapVerificationTypeInfoUninitialized.h>

PLJavaFile::PLJavaFile(uintptr_t address) : m_data((u1 *)address), m_constTable(0) {
    m_pos = 0;
    u4 magic = readInt();
    if(magic != 0xCAFEBABE) {
        printf("MAGIC mismatch!");
    } else {
        m_minorVersion = readUnsignedShort();
        m_majorVersion = readUnsignedShort();
        m_constCount = readUnsignedShort();
        u1 type;
        u2 len;
        char *str;
        char *posi;
        m_constTable = new PLJavaConst *[m_constCount];
        for(u2 i = 0 ; i < m_constCount - 1 ; i++) {
            type = (PLJavaConst::ConstTypes)readUnsignedByte();
            switch(type) {
                case PLJavaConst::ConstTypesClass:
                    m_constTable[i] = new PLJavaConstClassInfo(readUnsignedShort());
                    break;
                case PLJavaConst::ConstTypesFieldref:
                case PLJavaConst::ConstTypesMethodref:
                case PLJavaConst::ConstTypesInterfaceMethodref:
                    m_constTable[i] = new PLJavaConstRefInfo((PLJavaConst::ConstTypes)type, readUnsignedShort(), readUnsignedShort());
                    break;
                case PLJavaConst::ConstTypesString:
                    m_constTable[i] = new PLJavaConstTypesString(readUnsignedShort());
                    break;
                case PLJavaConst::ConstTypesInteger:
                    m_constTable[i] = new PLJavaConstTypesInteger(readInt());
                    break;
                case PLJavaConst::ConstTypesFloat:
                    m_constTable[i] = new PLJavaConstTypesFloat(readInt());
                    break;
                case PLJavaConst::ConstTypesLong:
                    m_constTable[i] = new PLJavaConstTypesLong(readInt(), readInt());
                    i++;
                    break;
                case PLJavaConst::ConstTypesDouble:
                    m_constTable[i] = new PLJavaConstTypesDouble(readInt(), readInt());
                    i++;
                    break;
                case PLJavaConst::ConstTypesNameAndType:
                    m_constTable[i] = new PLJavaConstNameAndType(readUnsignedShort(), readUnsignedShort());
                    break;
                case PLJavaConst::ConstTypesUtf8: {
                    len = readUnsignedShort();
                    str = new char[len + 1];
                    posi = (char *)(m_data + m_pos);
                    memcpy(str, posi, len);
                    str[len] = '\0';
                    m_constTable[i] = new PLJavaConstTypesUtf8(str, len);
                    m_pos += len;
                    break;
                }
                default:
                    printf("Error in const table. %d ", type);
                    break;
            }
        }
        m_accessFlags = PLJavaFile::AccessFlags(readUnsignedShort());
        m_class = (PLJavaConstClassInfo *)m_constTable[readUnsignedShort() - 1];
        u2 superclassIndex = readUnsignedShort();
        m_superClass = superclassIndex == 0 ? 0 : (PLJavaConstClassInfo *)m_constTable[superclassIndex - 1];
        u2 interfacesCount = readUnsignedShort();
        for(u2 i = 0 ; i < interfacesCount ; i++) {
            m_interfaces.add((PLJavaConstClassInfo *)m_constTable[readUnsignedShort() - 1]);
        }
        m_fieldCount = readUnsignedShort();
        for(u2 i = 0 ; i < m_fieldCount ; i++) {
            PLJavaFieldInfo *fields = new PLJavaFieldInfo((PLJavaFile::AccessFlags)readUnsignedShort(),
                                                          (PLJavaConstTypesUtf8 *)m_constTable[readUnsignedShort() - 1],
                                                          new PLJavaFieldDescriptor((PLJavaConstTypesUtf8 *)m_constTable[readUnsignedShort() - 1]));
            m_fieldInfos[i] = fields;
            addAttributes(fields);
        }
        m_methodeCount = readUnsignedShort();
        PLJavaConst::ConstTypes methodeType;
        PLJavaConstTypesUtf8 *methodeName;
        for(u2 i = 0 ; i < m_methodeCount ; i++) {
            methodeType = (PLJavaConst::ConstTypes)readUnsignedShort();
            methodeName = (PLJavaConstTypesUtf8 *)m_constTable[readUnsignedShort() - 1];
            PLJavaMethodeInfo *methods = new PLJavaMethodeInfo(methodeType, methodeName,
                                                               new PLJavaFieldDescriptor((PLJavaConstTypesUtf8 *)m_constTable[readUnsignedShort() - 1]));
            m_methodeInfos[i] = methods;
            m_methodeCache.insert(methodeName->string(), methods);
            addAttributes(methods);
            if(strcmp(methods->name()->string(), "main") == 0) {
                m_initMethode = methods;
            }
        }
        addAttributes(this);
    }
}

PLJavaFile::~PLJavaFile() {
    if(m_constTable) {
        delete[] m_constTable;
    }
}

void PLJavaFile::addAttributes(PLJavaAttributeInterface *objectToAdd) {
    u4 len;
    u2 maxStack;
    u2 maxLocals;
    u4 codeLength;
    u2 length;
    u2 nameIndex;
    const char *name;
    u2 _attributesCount = readUnsignedShort();
    for(u2 j = 0 ; j < _attributesCount ; j++) {
        nameIndex = readUnsignedShort();
        len = readInt();
        name = ((PLJavaConstTypesUtf8 *)m_constTable[nameIndex - 1])->string();
        if(strcmp(name, "Code") == 0) {
            maxStack = readUnsignedShort();
            maxLocals = readUnsignedShort();
            codeLength = readInt();
            PLJavaAttributeCode *c = new PLJavaAttributeCode(maxStack, maxLocals, codeLength, (uint8_t *)(m_data + m_pos), this);
            objectToAdd->addAttribute(c);
            m_pos += codeLength;
            length = readUnsignedShort();
            for(u2 k = 0 ; k < length ; k++) {
                c->addExceptionTable(new PLJavaAttributeExceptionTable(readUnsignedShort(), readUnsignedShort(), readUnsignedShort(), readUnsignedShort()));
            }
            addAttributes(c);
        } else if(strcmp(name, "LineNumberTable") == 0) {
            PLJavaAttributeLineNumberTable *c = new PLJavaAttributeLineNumberTable();
            objectToAdd->addAttribute(c);
            length = readUnsignedShort();
            for(u2 i = 0 ; i < length ; i++) {
                c->addLineNumber(new PLJavaAttributeLineNumberInfo(readUnsignedShort(), readUnsignedShort()));
            }
        } else if(strcmp(name, "SourceFile") == 0) {
            PLJavaAttributeSourceFile *c = new PLJavaAttributeSourceFile((PLJavaConstTypesUtf8 *)m_constTable[readUnsignedShort() - 1]);
            objectToAdd->addAttribute(c);
        } else if(strcmp(name, "StackMapTable") == 0) {
            PLJavaAttributeStackMapTable *c = new PLJavaAttributeStackMapTable();
            objectToAdd->addAttribute(c);
            length = readUnsignedShort();
            u1 frameType;
            for(u2 i = 0 ; i < length ; i++) {
                frameType = readUnsignedByte();
                if(frameType >= 0 && frameType <= 63) {
                    c->addStackMap(new PLJavaAttributeStackMapSame());
                } else if(frameType >= 64 && frameType <= 127) {
                    c->addStackMap(new PLJavaAttributeStackMapSameLocals1StackItem(getVerificationTypeInfo(readUnsignedByte())));
                } else if(frameType == 247) {
                    c->addStackMap(new PLJavaAttributeStackMapSameLocals1StackItemExtended(readUnsignedShort(), getVerificationTypeInfo(readUnsignedByte())));
                } else if(frameType >= 248 && frameType <= 250) {
                    c->addStackMap(new PLJavaAttributeStackMapChop(readUnsignedShort()));
                } else if(frameType == 251) {
                    c->addStackMap(new PLJavaAttributeStackMapSameFrameExtended(readUnsignedShort()));
                } else if(frameType >= 252 && frameType <= 254) {
                    u1 l = (u1)(frameType - 251);
                    u2 delta = readUnsignedShort();
                    PLJavaAttributeStackMapVerificationTypeInfo **infoList = 0;
                    if(l > 0) {
                        infoList = new PLJavaAttributeStackMapVerificationTypeInfo *[l];
                        for(u1 a = 0 ; a < l ; a++) {
                            infoList[a] = getVerificationTypeInfo(readUnsignedByte());
                        }
                    }
                    c->addStackMap(new PLJavaAttributeStackMapAppend(delta, len, infoList));
                } else if(frameType == 255) {
                    u2 delta = readUnsignedShort();
                    u2 numLocals = readUnsignedShort();
                    PLJavaAttributeStackMapVerificationTypeInfo **localsList = 0;
                    if(numLocals > 0) {
                        localsList = new PLJavaAttributeStackMapVerificationTypeInfo *[numLocals];
                        for(u2 a = 0 ; a < numLocals ; a++) {
                            localsList[a] = getVerificationTypeInfo(readUnsignedByte());
                        }
                    }
                    u2 numStacks = readUnsignedShort();
                    PLJavaAttributeStackMapVerificationTypeInfo **stacksList = 0;
                    if(numStacks > 0) {
                        stacksList = new PLJavaAttributeStackMapVerificationTypeInfo *[numStacks];
                        for(u2 a = 0 ; a < numStacks ; a++) {
                            stacksList[a] = getVerificationTypeInfo(readUnsignedByte());
                        }
                    }
                    c->addStackMap(new PLJavaAttributeStackMapFullFrame(delta, numLocals, localsList, numStacks, stacksList));
                } else {
                    printf("The frame type %d was not implemented.", frameType);
                }
            }
        } else {
            printf("Error attribute not implemented '%s'.", ((PLJavaConstTypesUtf8 *)m_constTable[nameIndex - 1])->string());
            m_pos += len;
        }
    }
}

PLJavaAttributeStackMapVerificationTypeInfo *PLJavaFile::getVerificationTypeInfo(u1 tag) {
    switch (tag) {
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemTop:
            return new PLJavaAttributeStackMapVerificationTypeInfoTop();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemInteger:
            return new PLJavaAttributeStackMapVerificationTypeInfoInteger();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemFloat:
            return new PLJavaAttributeStackMapVerificationTypeInfoFloat();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemLong:
            return new PLJavaAttributeStackMapVerificationTypeInfoLong();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemDouble:
            return new PLJavaAttributeStackMapVerificationTypeInfoDouble();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemNull:
            return new PLJavaAttributeStackMapVerificationTypeInfoNull();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemUninitializedThis:
            return new PLJavaAttributeStackMapVerificationTypeInfoUninitializedThis();
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemObject:
            return new PLJavaAttributeStackMapVerificationTypeInfoObjectVariableInfo(readUnsignedShort());
        case PLJavaAttributeStackMapVerificationTypeInfo::ItemUninitialized:
            return new PLJavaAttributeStackMapVerificationTypeInfoUninitialized(readUnsignedShort());
        default:
            printf("The tag value for get Verification Type Info %d was not implemented.", tag);
            break;
    }
    return 0;
}

void PLJavaFile::runInit() {
    PLJavaFrame *_frame = new PLJavaFrame(m_initMethode->code()->maxStack(), m_initMethode->code()->maxLocals());
    m_initMethode->code()->codeParser()->exec(_frame);
    delete _frame;
}

const PLJavaConst *PLJavaFile::constTableEntry(u2 index) {
    if(index >= m_constCount) {
        printf("The java file const value at index %d was not found.", index);
        return 0;
    }
    return m_constTable[index];
}

PLJavaConstTypesUtf8 *PLJavaFile::nameAtIndex(u2 index) {
    if(index > m_constCount) {
        printf("The java file const utf8 string value at index %d was not found.", index);
        return 0;
    }
    return (PLJavaConstTypesUtf8 *)m_constTable[index - 1];
}

PLJavaMethodeInfo *PLJavaFile::methodeAt(u2 index) {
    if(index >= m_constCount) {
        printf("The java file methode info value at index %d was not found.", index);
        return 0;
    }
    return m_methodeInfos[index];
}

PLJavaMethodeInfo *PLJavaFile::methode(const char *name) {
    return m_methodeCache.lookup(name);
}

void PLJavaFile::addAttribute(PLJavaAttribute *newAttribute) {
    m_attributes.add(newAttribute);
}

PLJavaAttribute *PLJavaFile::attributeAt(u2 index) {
    if(index >= m_constCount) {
        printf("The java file attribute value at index %d was not found.", index);
        return 0;
    }
    return m_attributes.at(index);
}

PLJavaFieldInfo *PLJavaFile::fieldAt(u2 index) {
    if(index >= m_fieldCount) {
        printf("The java file field value at index %d was not found.", index);
        return 0;
    }
    return m_fieldInfos[index];
}
